package de.slothsoft.commons.swing;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPasswordField;
import javax.swing.text.JTextComponent;
import javax.xml.bind.DataBindingException;


public final class DataBinding<T> {

    // FIXME: refactor data binding, using interfaces or something to hook
    // custom controls

    private final List<Binding> bindings = new ArrayList<>();
    private T model;

    public DataBinding(T model) {
	this.model = model;
    }

    public Binding bind(JComponent component, T model, String property) {
	Binding binding = new Binding();
	binding.component = component;
	binding.property = property;
	this.bindings.add(binding);
	return binding;
    }

    public void updateTargets() throws DataBindingException {
	for (Binding binding : this.bindings) {
	    updateTarget(binding);
	}
    }

    public void updateModels() throws DataBindingException {
	for (Binding binding : this.bindings) {
	    updateModel(binding);
	}
    }

    private void updateTarget(Binding binding) throws DataBindingException {
	if (binding.component instanceof JTextComponent) {
	    ((JTextComponent) binding.component).setText(DisplayUtil.toString(PropertyUtil.invokeGetter(this.model,
		    binding.property)));
	} else if (binding.component instanceof JCheckBox) {
	    ((JCheckBox) binding.component).setSelected((Boolean) PropertyUtil.invokeGetter(this.model,
		    binding.property));
	} else if (binding.component instanceof JComboBox) {
	    ((JComboBox<?>) binding.component).setSelectedItem(PropertyUtil.invokeGetter(this.model, binding.property));
	}
    }

    protected void updateModel(Binding binding) throws DataBindingException {
	if (binding.component instanceof JPasswordField) {
	    updateModelPasswordField((JTextComponent) binding.component, binding);
	} else if (binding.component instanceof JTextComponent) {
	    updateModelTextField((JTextComponent) binding.component, binding);
	} else if (binding.component instanceof JCheckBox) {
	    updateModelCheckBox((JCheckBox) binding.component, binding);
	} else if (binding.component instanceof JComboBox) {
	    updateModelComboBox((JComboBox<?>) binding.component, binding);
	}
    }

    private void updateModelTextField(JTextComponent text, Binding binding) {
	Method getter = PropertyUtil.getGetter(this.model.getClass(), binding.property);
	invokeSetter(text, binding, getter.getReturnType());
    }

    private void updateModelPasswordField(JTextComponent text, Binding binding) {
	invokeSetter(text, binding, char[].class);
    }

    private void invokeSetter(JTextComponent text, Binding binding, Class<?> returnType) {
	Object value = StringParser.parse(text.getText(), returnType);
	PropertyUtil.invokeSetter(this.model, binding.property, value, returnType);
    }

    private void updateModelCheckBox(JCheckBox component, Binding binding) {
	PropertyUtil.invokeSetter(this.model, binding.property, component.isSelected(), boolean.class);
    }

    private void updateModelComboBox(JComboBox<?> component, Binding binding) {
	Object selected = component.getSelectedItem();
	Method getter = PropertyUtil.getGetter(this.model.getClass(), binding.property);
	PropertyUtil.invokeSetter(this.model, binding.property, selected, getter.getReturnType());
    }

}
